package httpdriver

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/samber/lo"

	"github.com/openmeterio/openmeter/api"
	"github.com/openmeterio/openmeter/openmeter/customer"
	plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription"
	"github.com/openmeterio/openmeter/openmeter/subscription"
	subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow"
	"github.com/openmeterio/openmeter/pkg/convert"
	"github.com/openmeterio/openmeter/pkg/framework/commonhttp"
	"github.com/openmeterio/openmeter/pkg/framework/transport/httptransport"
	"github.com/openmeterio/openmeter/pkg/models"
)

type (
	CreateSubscriptionRequest = plansubscription.CreateSubscriptionRequest

	CreateSubscriptionResponse = api.Subscription
	CreateSubscriptionHandler  = httptransport.Handler[CreateSubscriptionRequest, CreateSubscriptionResponse]
)

func (h *handler) CreateSubscription() CreateSubscriptionHandler {
	return httptransport.NewHandler(
		func(ctx context.Context, r *http.Request) (CreateSubscriptionRequest, error) {
			body := api.CreateSubscriptionJSONRequestBody{}

			if err := commonhttp.JSONRequestBodyDecoder(r, &body); err != nil {
				return CreateSubscriptionRequest{}, err
			}

			ns, err := h.resolveNamespace(ctx)
			if err != nil {
				return CreateSubscriptionRequest{}, fmt.Errorf("failed to resolve namespace: %w", err)
			}

			// Any transformation function generated by the API will succeed if the body is serializable, so we have to check for the presence of
			// fields to determine what body type we're dealing with
			type testForCustomPlan struct {
				CustomPlan any `json:"customPlan"`
			}

			var t testForCustomPlan

			bodyBytes, err := json.Marshal(body)
			if err != nil {
				return CreateSubscriptionRequest{}, fmt.Errorf("failed to marshal request body: %w", err)
			}

			if err := json.Unmarshal(bodyBytes, &t); err != nil {
				return CreateSubscriptionRequest{}, fmt.Errorf("failed to unmarshal request body: %w", err)
			}

			if t.CustomPlan != nil {
				// Custom subscription creation
				parsedBody, err := body.AsCustomSubscriptionCreate()
				if err != nil {
					return CreateSubscriptionRequest{}, fmt.Errorf("failed to decode request body: %w", err)
				}

				req, err := CustomPlanToCreatePlanRequest(parsedBody.CustomPlan, ns)
				if err != nil {
					return CreateSubscriptionRequest{}, fmt.Errorf("failed to create plan request: %w", err)
				}

				plan := plansubscription.PlanInput{}
				plan.FromInput(&req)

				timing := subscription.Timing{
					Enum: lo.ToPtr(subscription.TimingImmediate),
				}
				if parsedBody.Timing != nil {
					timing, err = MapAPITimingToTiming(*parsedBody.Timing)
					if err != nil {
						return CreateSubscriptionRequest{}, fmt.Errorf("failed to map timing: %w", err)
					}
				}

				// Get the customer
				cus, err := h.getCustomer(ctx, ns, parsedBody.CustomerId, parsedBody.CustomerKey)
				if err != nil {
					return CreateSubscriptionRequest{}, fmt.Errorf("failed to get customer: %w", err)
				}

				return CreateSubscriptionRequest{
					WorkflowInput: subscriptionworkflow.CreateSubscriptionWorkflowInput{
						ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{
							Timing:      timing,
							Name:        req.Name,        // We map the plan name to the subscription name
							Description: req.Description, // We map the plan description to the subscription description
							MetadataModel: models.MetadataModel{
								Metadata: req.Metadata, // We map the plan metadata to the subscription metadata
							},
						},
						Namespace:     ns,
						CustomerID:    cus.ID,
						BillingAnchor: parsedBody.BillingAnchor,
					},
					PlanInput: plan,
				}, nil
			} else {
				// Plan subscription creation
				parsedBody, err := body.AsPlanSubscriptionCreate()
				if err != nil {
					return CreateSubscriptionRequest{}, fmt.Errorf("failed to decode request body: %w", err)
				}

				plan := plansubscription.PlanInput{}
				plan.FromRef(&plansubscription.PlanRefInput{
					Key: parsedBody.Plan.Key,
				})

				timing := subscription.Timing{
					Enum: lo.ToPtr(subscription.TimingImmediate),
				}
				if parsedBody.Timing != nil {
					timing, err = MapAPITimingToTiming(*parsedBody.Timing)
					if err != nil {
						return CreateSubscriptionRequest{}, fmt.Errorf("failed to map timing: %w", err)
					}
				}

				// Get the customer
				customer, err := h.getCustomer(ctx, ns, parsedBody.CustomerId, parsedBody.CustomerKey)
				if err != nil {
					return CreateSubscriptionRequest{}, fmt.Errorf("failed to get customer: %w", err)
				}

				return CreateSubscriptionRequest{
					WorkflowInput: subscriptionworkflow.CreateSubscriptionWorkflowInput{
						ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{
							Timing:      timing,
							Name:        lo.FromPtr(parsedBody.Name),
							Description: parsedBody.Description,
							MetadataModel: models.MetadataModel{
								Metadata: convert.DerefHeaderPtr[string](parsedBody.Metadata),
							},
						},
						Namespace:     ns,
						CustomerID:    customer.ID,
						BillingAnchor: parsedBody.BillingAnchor,
					},
					PlanInput:     plan,
					StartingPhase: parsedBody.StartingPhase,
				}, nil
			}
		},
		func(ctx context.Context, request CreateSubscriptionRequest) (CreateSubscriptionResponse, error) {
			res, err := h.PlanSubscriptionService.Create(ctx, request)
			if err != nil {
				return CreateSubscriptionResponse{}, err
			}

			return MapSubscriptionToAPI(res), nil
		},
		commonhttp.JSONResponseEncoderWithStatus[CreateSubscriptionResponse](http.StatusCreated),
		httptransport.AppendOptions(
			h.Options,
			httptransport.WithOperationName("createSubscription"),
			httptransport.WithErrorEncoder(errorEncoder()),
		)...,
	)
}

// getCustomer gets a customer by ID or key.
func (h *handler) getCustomer(ctx context.Context, namespace string, id *string, key *string) (*customer.Customer, error) {
	var (
		cus *customer.Customer
		err error
	)

	switch {
	case id != nil && *id != "":
		cus, err = h.CustomerService.GetCustomer(ctx, customer.GetCustomerInput{
			CustomerID: &customer.CustomerID{
				ID:        *id,
				Namespace: namespace,
			},
		})
		if err != nil {
			return nil, fmt.Errorf("failed to lookup customer by id [namespace=%s customer.id=%s]: %w", namespace, *id, err)
		}

	case key != nil && *key != "":
		cus, err = h.CustomerService.GetCustomer(ctx, customer.GetCustomerInput{
			CustomerKey: &customer.CustomerKey{
				Key:       *key,
				Namespace: namespace,
			},
		})
		if err != nil {
			return nil, fmt.Errorf("failed to lookup customer by key [namespace=%s customer.id=%s]: %w", namespace, *key, err)
		}

	default:
		return nil, models.NewGenericValidationError(fmt.Errorf("customer id or key is required"))
	}

	if cus != nil && cus.IsDeleted() {
		return nil, models.NewGenericPreConditionFailedError(
			fmt.Errorf("customer is deleted [namespace=%s customer.id=%s]", cus.Namespace, cus.ID),
		)
	}

	return cus, err
}
